/*
 * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
 * subject to license terms.
 */

package org.gwt.beansbinding.ui.client.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.gwt.beansbinding.core.client.Binding;
import org.gwt.beansbinding.core.client.PropertyStateEvent;
import org.gwt.beansbinding.core.client.PropertyStateListener;
import org.gwt.beansbinding.observablecollections.client.ObservableList;
import org.gwt.beansbinding.observablecollections.client.ObservableListListener;

/**
 * @author sky
 * @author Shannon Hickey
 */
public abstract class ListBindingManager implements ObservableListListener {
  private AbstractColumnBinding[] bindings;
  private ReusableBinding reusableBinding;
  private List<?> elements;
  private List<ColumnDescriptionManager> managers;

  public ListBindingManager() {
    bindings = getColBindings();
  }

  private List<ColumnDescriptionManager> createManagers(
      AbstractColumnBinding[] bindings) {
    List<ColumnDescriptionManager> managers = new ArrayList<ColumnDescriptionManager>(
        bindings.length);

    for (AbstractColumnBinding binding : bindings) {
      managers.add(new ColumnDescriptionManager(binding));
    }

    return managers;
  }

  protected abstract AbstractColumnBinding[] getColBindings();

  public void setElements(List<?> elements, boolean sendAllChanged) {
    if (this.elements != null) {
      if (this.elements instanceof ObservableList) {
        ((ObservableList) this.elements).removeObservableListListener(this);
      }

      if (managers != null) {
        for (ColumnDescriptionManager manager : managers) {
          manager.stopListening();
        }
      }
    }

    managers = null;
    reusableBinding = null;
    this.elements = (elements == null) ? Collections.emptyList() : elements;

    boolean addListeners = false;

    if (elements instanceof ObservableList) {
      ((ObservableList) elements).addObservableListListener(this);
      addListeners = !((ObservableList) elements).supportsElementPropertyChanged();
    } else if (elements != null) {
      addListeners = true;
    }

    if (bindings.length != 0) {
      reusableBinding = new ReusableBinding(bindings[0]);
    }

    if (addListeners) {
      managers = createManagers(getColBindings());
      for (ColumnDescriptionManager manager : managers) {
        manager.startListening();
      }
    }

    if (sendAllChanged) {
      allChanged();
    }
  }

  public final Object getElement(int index) {
    return elements.get(index);
  }

  public final List<?> getElements() {
    return elements;
  }

  public final int size() {
    return (elements == null) ? 0 : elements.size();
  }

  public final Object valueAt(int row, int column) {
    if (managers != null) {
      // Make sure the necessary listeners have been registered
      for (ColumnDescriptionManager manager : managers) {
        manager.validateBinding(row);
      }
    }

    reusableBinding.setBaseAndSource(bindings[column], elements.get(row));
    Binding.ValueResult result = reusableBinding.getSourceValueForTarget();
    return result.failed() ? null : result.getValue();
  }

  public final int columnCount() {
    return bindings.length;
  }

  public final void listElementsAdded(ObservableList list, int index, int length) {
    if (length == 0) {
      return;
    }

    if (managers != null) {
      for (ColumnDescriptionManager manager : managers) {
        manager.add(index, length);
      }
    }

    added(index, length);
  }

  public final void listElementsRemoved(ObservableList list, int index,
      List elements) {
    if (elements.size() == 0) {
      return;
    }

    if (managers != null) {
      for (ColumnDescriptionManager manager : managers) {
        manager.remove(index, elements.size());
      }
    }

    removed(index, elements.size());
  }

  public final void listElementReplaced(ObservableList list, int index,
      Object oldElement) {
    if (managers != null) {
      for (ColumnDescriptionManager manager : managers) {
        manager.replaced(index);
      }
    }

    changed(index);
  }

  public final void listElementPropertyChanged(ObservableList list, int index) {
    changed(index);
  }

  protected abstract void allChanged();

  protected abstract void valueChanged(int row, int column);

  protected abstract void added(int index, int length);

  protected abstract void removed(int index, int length);

  protected abstract void changed(int row);

  private final class ColumnDescriptionManager {
    private final AbstractColumnBinding columnBinding;
    private List<EntryWrapper> wrappers;

    ColumnDescriptionManager(AbstractColumnBinding columnBinding) {
      this.columnBinding = columnBinding;
    }

    public void startListening() {
      int size = elements.size();
      wrappers = new ArrayList<EntryWrapper>(size);
      for (int i = 0; i < size; i++) {
        wrappers.add(null);
      }
    }

    public void stopListening() {
      for (EntryWrapper wrapper : wrappers) {
        if (wrapper != null) {
          wrapper.stopListening();
        }
      }

      wrappers = null;
    }

    public void validateBinding(int row) {
      if (wrappers.get(row) == null) {
        EntryWrapper wrapper = new EntryWrapper(getElement(row));
        wrappers.set(row, wrapper);
      }
    }

    void wrapperChanged(EntryWrapper wrapper) {
      int row = wrappers.indexOf(wrapper);
      ListBindingManager.this.valueChanged(row, columnBinding.getColumn());
    }

    private void add(int index, int length) {
      for (int i = 0; i < length; i++) {
        wrappers.add(index, null);
      }
    }

    private void remove(int index, int length) {
      while (length-- > 0) {
        EntryWrapper wrapper = wrappers.remove(index);
        if (wrapper != null) {
          wrapper.stopListening();
        }
      }
    }

    private void replaced(int index) {
      EntryWrapper wrapper = wrappers.get(index);
      if (wrapper != null) {
        wrapper.stopListening();
      }
      wrappers.set(index, null);
    }

    private final class EntryWrapper implements PropertyStateListener {
      private Object source;

      EntryWrapper(Object source) {
        this.source = source;
        columnBinding.getSourceProperty().addPropertyStateListener(source, this);
      }

      public void stopListening() {
        columnBinding.getSourceProperty().removePropertyStateListener(source,
            this);
        source = null;
      }

      public void propertyStateChanged(PropertyStateEvent pse) {
        if (pse.getValueChanged()) {
          wrapperChanged(this);
        }
      }
    }
  }

  private final class ReusableBinding extends Binding {
    public ReusableBinding(AbstractColumnBinding base) {
      super(null, base.getSourceProperty(), null, base.getTargetProperty(),
          null);
    }

    public void setBaseAndSource(AbstractColumnBinding base, Object source) {
      setSourceProperty(base.getSourceProperty());
      setTargetProperty(base.getTargetProperty());
      setSourceObject(source);
      setConverter(base.getConverter());
      setSourceNullValue(base.getSourceNullValue());
      if (base.isSourceUnreadableValueSet()) {
        setSourceUnreadableValue(base.getSourceUnreadableValue());
      } else {
        unsetSourceUnreadableValue();
      }
    }

    public final void bindImpl() {
    }

    public final void unbindImpl() {
    }

  }

}
